require 'json'
require 'open-uri'
require 'pp' # pretty print
require 'stringio'

class DataPage < Page  
  include ActionView::Helpers::NumberHelper
  include ActionView::Helpers::TextHelper
  
  desc %{
    The outer declaration of a data template.  
    Requires a 'src' attribute that opens a URL to a JSON data store
  
    *Usage:* 
    <pre><code><r:data src="http://localhost:3000/foo/bar.js">...</r:data></code></pre>
  }
  tag "data" do |tag|
    src = src(tag)
    file = open(src)
    tag.locals.data = JSON.parse(file.read)
    tag.expand
  end

  desc %{
    A tag for outputing data from inside an <r:data> tag.
    Optionally take a 'key' attribute that changes the data context to the key specified.
    
    *Usage:*
    <pre><code><r:value [key='foo']></r:value></code></pre>
  }
  tag "data:value" do |tag|
    if key = tag.attr['key']
      # Reset the locals.data so that tags below inherit the same data context
      tag.locals.data = tag.locals.data[key]
    end

    return_data_or_expand(tag)
  end
  
  desc %{
    Iterates over data applying the enclosed block to each.
    
    *Usage:*
    <pre><code><r:values [key="foo"]>...</r:values></code></pre>
  }
  tag "data:values" do |tag|
    if key = tag.attr['key']
      # Reset the locals.data so that tags below inherit the same data context
      tag.locals.data = tag.locals.data[key]
    end

    return "" if !tag.locals.data || tag.locals.data.empty?

    tag.locals.data.inject("") do |result, data|
      tag.locals.data = data
      result << tag.expand
    end
  end
  
  desc %{
    Conditionally expands the contained block.  Optionally, a 'test' or 'key' attribute
    can be specified which will be the tested data.  If a 'key' attribute is specified, 
    the data context is changed to that and it's used as the test.  If a 'test' attribute 
    is specified, the data context remains the same.

    *Usage:*
    <pre><code><:r:if_value [test="..."] [key="..."]>...</r:if_value></code></pre>
  }
  tag "data:if_value" do |tag|
    conditional_expansion(tag)
  end
  
  desc %{
    The same as if_value except the test is negated.
    
    *Usage:*
    <pre><code><:r:unless_value [test="..."] [key="..."]>...</r:unless_value></code></pre>
  }
  tag "data:unless_value" do |tag|
    conditional_expansion(tag, true)    
  end

  # Data formatters

  desc %{
    Applies the number_with_precision helper to the data.  Can be a double tag to nest other formatters.
    
    *Usage*
    <pre><code><r:round [precision="3"]>...</r:round></code></pre>
  }
  tag "data:round" do |tag|
    precision = tag.attr['precision'] || 3
    tag.locals.data = number_with_precision(tag.locals.data, precision)

    return_data_or_expand(tag)
  end
  
  desc %{
    Applies the number_with_delimiter helper to the data.  Can be a double tag to nest other formatters.
    
    *Usage*
    <pre><code><r:delimiter [delimiter=","] [separator="."]>...</r:round></code></pre>
  }
  tag "data:delimiter" do |tag|
    delimiter = tag.attr['delimiter'] || ","
    separator = tag.attr['separator'] || "."
    tag.locals.data = number_with_delimiter(tag.locals.data, delimiter, separator)
    
    return_data_or_expand(tag)
  end

  desc %{
    Applies the number_to_currency helper to the data.  Can be a double tag to nest other formatters.
    
    *Usage*
    <pre><code><r:currency [precision="2"] [unit="$"] [delimiter=","] [separator="."]>...</r:round></code></pre>
  }
  tag "data:currency" do |tag|
    options = {
      :precision => tag.attr['precision'] ? tag.attr['precision'].to_i : nil,
      :unit => tag.attr['unit'],
      :delimiter => tag.attr['delimiter'],
      :separator => tag.attr['separator']
    }.delete_if {|k,v| v.nil?}
    tag.locals.data = number_to_currency(tag.locals.data, options)
    
    return_data_or_expand(tag)
  end
  
  # Debuggging
  
  desc %{
    Pretty prints the current data if that page's status id "Draft"
  }
  tag "data:echo" do |tag|
    if status == Status[:draft]
      # Set up a StringIO that PP.pp will write to
      io = PP.pp(tag.locals.data, StringIO.new)
      # ... rewind it back to the beginning
      io.rewind
      pretty_string = CGI.escapeHTML(io.read)
      %{<pre><code>#{pretty_string}</code></pre>}
    end
  end

  protected
  # Gets the source url for data.  Broken into a method for easy overriding.
  def src(tag)
    tag.attr['src'] or raise TagError, "'data' tags require a 'src' attribute"
  end
  
  # Refactored guts of if_value and unless_value
  def conditional_expansion(tag, negate_test = false)
    if test_key = tag.attr['test']
      test = tag.locals.data[test_key]
    elsif key = tag.attr['key']
      test = tag.locals.data = tag.locals.data[key]
    else
      test = tag.locals.data
    end
    
    test = !test if negate_test
    
    tag.expand if test
  end
  
  # Refactored code to allow a tag to either return the data context or expand
  def return_data_or_expand(tag)
    if tag.double?
      tag.expand
    else
      CGI.escapeHTML(tag.locals.data.to_s)
    end
  end
end
